/**
 ****************************************************************************
 * Copyright (C) Marcelo F. Ochoa. All rights reserved.                      *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included  with this distribution in *
 * the LICENSE file.                                                         *
 */

package org.apache.cocoon.generation;

import com.prism.Content;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.excalibur.xml.Parser;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.Constants;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.acting.DBPrismAction;
import org.apache.cocoon.caching.CacheValidity;
import org.apache.cocoon.caching.Cacheable;
import org.apache.cocoon.caching.ExternalCacheValidity;
import org.apache.cocoon.caching.NOPCacheValidity;
import org.apache.cocoon.components.cache.ESIKey;
import org.apache.cocoon.components.cache.Server;
import org.apache.cocoon.environment.Context;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Response;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.environment.http.HttpEnvironment;
import org.apache.cocoon.environment.wrapper.CocoonRequestWrapper;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.prism.DBPrism;
import com.prism.NotAuthorizedException;
import com.prism.utils.Block;

/**
 * Parallel content agregation Problem: DBPrismGenrator divides the XML generation in two stages
 * at setup time DBPrismGenerator creates a private Thread (delayer) which
 * calls the DBPrism.makePage method, then permits Cocoon2's threads to continue with other process.
 * At setup time DBPrismGenerator uses a Block class (blocker) as a private semaphore which
 * locks the generate method until the makePage operation is finished.
 * When makePage finish (run method of Delayer) unlock the semaphore permiting
 * the DBPrismGenerator to continue with the generation step calling to DBPrism.getPage() flow:
 * - Cocoon2 setup stage (in parallel) --> setup (part1) (Start a private engine 1 in parallel)
 * --> setup (part2) (Start a private engine 2 in parallel) --> setup (partn) (Start a private engine 1 in parallel)
 * - Cocoon2 generate stage (Sequencially gets the content of the parts) --> generate (part1) (Wait until engine 1 finish)
 * --> generate (part2) (Wait until engine 2 finish) --> generate (partn) (Wait until engine n finish) - Serialize stage.<BR>
 * <BR>
 * Allows DB Prism to be used as Cocoon2's generator.
 * @author <a href="mailto:mochoa@ieee.org">Marcelo F. Ochoa</a><BR> 
 * <BR>
 * Modified: 4/Nov/2003 by <a href="mailto:pyropunk@usa.net">Alexander Graesser</a> (LXG)<BR>
 * Changes : <UL><LI>Added log4j logging</LI>
 *           <LI>JavDoc cleanup</LI>
 *           <LI>code cleanup</LI></UL> 
 */
public class DBPrismGenerator extends ComposerGenerator implements Composable, Configurable, Recyclable,
    Cacheable, Contextualizable {
        protected Request request = null;
        protected Response response = null;
        protected Context context = null;
        protected HttpServletRequest httpRequest = null;
        protected DBPrism engine = null;
        protected CocoonRequestWrapper req = null;
        protected DBPrismGenerator.Delayer delayer = null;
        protected Block blocker = null;
        protected Server cacheServer = null;
        protected ESIKey key = null;
        protected int cacheControl = 0;
        
        // DBPrismGenerator as action
        private Redirector actionRedirector;
        private Map actionResultMap;

        /**
         * Contextualize this class. Stores context information to be use by configure method in order to resolve
         * /prism.properties as $WEBAPP/prism.properties in servlet 2.2+ containers
         */
        public void contextualize(org.apache.avalon.framework.context.Context ctx) throws ContextException {
            //System.out.println("contextualize called id="+this.toString());
            if (ctx==null)
              throw new ContextException("DBPrismGenerator requires context object.");
            else
              this.context = (Context)ctx.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
        }

        /**
         * Set the sitemap-provided configuration.
         * @param conf The configuration information
         * @exception ConfigurationException For example (on Cocoon sitemap.xmap): 
         * <map:generators default="file"> ....
         *   <map:generator  name="db" src="org.apache.cocoon.generation.DBPrismGenerator"
         *                   properties="/prism.properties"/> 
         *   .... 
         * </map:generators>
         */
        public void configure(Configuration conf) throws ConfigurationException {
            String propfilename = conf.getAttribute("properties", DBPrism.PROPERTIES);
            try {
                if (propfilename.equals("/WEB-INF"+DBPrism.PROPERTIES)) // converts to CONTEXT form
                        propfilename = this.context.getResource("/WEB-INF/").toExternalForm() + DBPrism.PROPERTIES.substring(1);
                //System.out.println("configure called properties="+propfilename);
                DBPrism.initDBPrism(propfilename);
            } catch (Exception e) {
                throw new ConfigurationException("Can't init DB Prism Engine",e);
            }
        }

        /**
         * Generate the unique key. This key must be unique inside the space of this component.
         * @return The generated key hashes the src
         */
        public long generateKey() {
            try {
                if (this.cacheControl == Server.EXTERNAL)
                   return this.key.getKey();
                else
                   return 0;
            } catch (Exception pe) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("generateKey Exception, returning 0", pe);
                return 0;
            }
        }

        /**
         * Generate the validity object.
         * @return The generated validity object or <code>null</code> if the component is currently not cacheable.
         */
        public CacheValidity generateValidity() {
            //System.out.println("generateValidity called");
            if (this.cacheControl == Server.NOP) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("generateValidity called returning NOPCacheValidity");
                return new NOPCacheValidity();
            } else if (this.cacheControl == Server.EXTERNAL) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("generateValidity called returning ExternalCacheValidity for "+this.source+"?"+this.key.getEncodedArgs());
                return new ExternalCacheValidity(this.key);
            }
            else {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("generateValidity called returning null");
                return null;
            }
        }

        /** Recycle the generator by removing references */
        public void recycle() {
            if (blocker != null) {
                blocker.lock(engine); // Block this engine, wait until Delayer finish
                try {
                    // free dbprism connection
                    if (engine!=null) { 
                      engine.recycle(this.req);
                      this.blocker.unlock(engine);
                    }
                } catch (SQLException sqe) { 
                  // TODO: Ignore because?? 
                } 
            }
            if (manager!=null && cacheServer!=null) {
              // LXG: no need to cast
              this.manager.release(this.cacheServer); // free cache server 
            }
            super.recycle();
            this.request = null;
            this.response = null;
            this.context = null;
            this.httpRequest = null;
            this.engine = null;
            this.req = null;
            this.delayer = null;
            this.blocker = null;
            this.cacheServer = null;
            this.key = null;
            if (getLogger().isDebugEnabled())
                getLogger().debug("Recycle called instance=" + this);
            //System.out.println("Recycle called id="+this.toString());
        }

        public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) 
          throws ProcessingException,SAXException, IOException {
                String cacheControlStr;
                //System.out.println("setup called id="+this.toString());
                super.setup(resolver, objectModel, src, par);
                if (getLogger().isDebugEnabled())
                    getLogger().debug("setup called url: " + src + " instance=" + this.toString());
                try {
                    this.cacheServer = (Server)this.manager.lookup(Server.ROLE);
                } catch (ComponentException ce) {
                    throw new ProcessingException("Can't get External invalidator server instance",ce);
                }
                this.request  = ObjectModelHelper.getRequest(objectModel);
                this.context  = ObjectModelHelper.getContext(objectModel);
                this.response = ObjectModelHelper.getResponse(objectModel);
                // ensure that we are running in a servlet environment
                this.httpRequest = (HttpServletRequest)objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT);
                if (this.httpRequest == null) {
                    throw new ProcessingException("HttpServletRequest object not available");
                }
                cacheControlStr = par.getParameter(Server.CACHE_CONTROL_ARG_NAME, Server.CACHE_CONTROL_NONE);
                if (Server.CACHE_CONTROL_EXTERNAL.equalsIgnoreCase(cacheControlStr)) {
                    cacheControl = Server.EXTERNAL;
                    try { // only with Cache-Control=External
                      this.key = new ESIKey(this.source,this.parameters, this.httpRequest);
                    } catch (ParameterException pe) {
                      throw new ProcessingException("DBPrismGenerator can't encode arguments",pe);
                    }
                    if (!this.key.wasNew()) {
                      // do not start page generation
                      if (getLogger().isDebugEnabled())
                        getLogger().debug("found a cached page for key: " + this.key.getKey());
                       //System.out.println("setup found a cached page");
                       return;
                   }
                } else if (Server.CACHE_CONTROL_NOP.equalsIgnoreCase(cacheControlStr))
                    cacheControl = Server.NOP;
                else
                    cacheControl = Server.NONE;
                if (getLogger().isDebugEnabled())
                    getLogger().debug("executing request:" + this.source + " instance=" + this.toString());
                req = new CocoonRequestWrapper(httpRequest, this.source, par);
                engine = new DBPrism();
                blocker = new Block();
                blocker.lock(engine); // Block this engine until the delayer is started
                delayer = new DBPrismGenerator.Delayer(engine, req, blocker);
                delayer.start(); // Sent engine.makePage() message in background
                //System.out.println("setup starts a page generation with engine="+engine.toString());
        }

        /**
         * Generate XML data from DB Prism Engine. Wait until delayer finish his engine.MakePage call
         * and call to engine.getPage if everything is OK.
         */
        // LXG: removed IOException as it is not thrown
        // public void generate() throws ProcessingException, IOException {
        public void generate() throws ProcessingException {
            long startTime = System.currentTimeMillis();
            //System.out.println("generate called id="+this.toString());
            //System.out.println("generate called with engine="+engine.toString());
            Parser parser = null;
            HttpServletResponse httpResponse = (HttpServletResponse)this.objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT);
            if (httpResponse == null) {
                throw new ProcessingException("HttpServletResponse object not available");
            }
            if (engine == null) {
                throw new ProcessingException("Engine object not available");
            }
            // Gets components stored if DBPrismGenerator is used as Action
            this.actionRedirector = (Redirector)this.objectModel.get(DBPrismAction.REDIRECTOR_OBJECT);
            this.actionResultMap = (Map)this.objectModel.get(DBPrismAction.ACTION_RESULT_OBJECT);
            
            try {
                // pipe the results into the parser
                parser = (Parser)this.manager.lookup(Parser.ROLE);
                //parser.setConsumer(this.xmlConsumer);
                blocker.lock(engine); // Block this engine, wait until Delayer finish
                if (delayer.status == Delayer.NOTAUTOHRIZED) {
                    sendUnauthorized(httpResponse, delayer.msg);
                    try {
                        parser = (Parser)this.manager.lookup(Parser.ROLE);
                        //parser.setConsumer(this.xmlConsumer);
                        parser.parse(new InputSource(new StringReader(DBPrism.UnauthorizedText)),this.xmlConsumer);
                    } catch (SAXException e) {
                        if (getLogger().isDebugEnabled()) {
                            getLogger().debug("SAXException in generate()", e);
                            getLogger().debug("Embedded SAXException generate()", e.getException());
                        }
                        throw new ProcessingException("SAXException", e);
                    } catch (ComponentException e) {
                        if (getLogger().isDebugEnabled()) {
                            getLogger().debug("ComponentException in generate()", e);
                            getLogger().debug("Embedded ComponentException in generate()", e);
                        }
                        throw new ProcessingException("ComponentException", e);
                    }
                } else if (delayer.status == Delayer.GENERALERROR) {
                    if (getLogger().isDebugEnabled())
                        getLogger().debug("GeneralError in generate(): " + delayer.msg);
                    throw new ProcessingException("General Error: " + delayer.msg);
                }
                else if (delayer.status == Delayer.STOPED) {
                    if (getLogger().isDebugEnabled())
                        getLogger().debug("GeneralError in generate(): Delayer not started");
                    throw new ProcessingException("General Error: Delayer not started");
                }
                else if (delayer.status == Delayer.FINISHED) {
                    showPage(httpResponse, parser, engine.getPage(req));
                    if (this.cacheControl == Server.EXTERNAL)
                        this.cacheServer.registerCacheablePage(this.key);
                }
            } catch (SAXException e) {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("SAXException generate()", e);
                    getLogger().debug("Embedded SAXException generate()", e.getException());
                }
                throw new ProcessingException("SAXException", e);
            } catch (IOException e) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("IOException in generate()", e);
                throw new ProcessingException("IOException", e);
            } catch (Exception e) {
                if (getLogger().isDebugEnabled())
                    getLogger().debug("Exception in generate()", e);
                throw new ProcessingException("Exception", e);
            } finally {
                blocker.unlock(engine); // unlock the engine
                try {
                  engine.recycle(httpRequest);
                } catch (SQLException sqe) {
                    getLogger().error("Exception releasing engine",sqe);
                }
                // Cleanup local resources
                if (parser != null) this.manager.release(parser);
                if (getLogger().isDebugEnabled())
                    getLogger().debug("processing time " + (System.currentTimeMillis() - startTime) + "ms.");
            }
        }

        // LXG: removed IOException as it is not thrown
        // public void sendUnauthorized(HttpServletResponse res, String msg) throws IOException {
        public void sendUnauthorized(HttpServletResponse res, String msg) {
            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            // put your realm here
            res.setHeader("WWW-authenticate", "basic realm=\"" + msg + "\"");
        }

        /**
         * Process cookie definicion from Jxtp.sendCookie or Jxtp.removeCookie
         * Syntax definition from http://www.netscape.com/newsref/std/cookie_spec.html See Jxtp package
         * Don't work with cookie definition greater than 255 char's (muti-line cookies)
         */
        public Cookie Make_Cookie(String s) {
            Cookie choc_chip;
            long age;
            // Divide the string cookie format in fields by the ;
            StringTokenizer st = new StringTokenizer(s, ";");
            // the name = value pairs is required
            String s1 = (String)st.nextElement();
            choc_chip = new Cookie(s1.substring(0, s1.indexOf("=")), s1.substring(s1.indexOf("=") + 1));
            //System.out.println("Name =>" + choc_chip.getName());
            //System.out.println("Value =>" + choc_chip.getValue());
            while (st.hasMoreElements()) {
                s1 = (String)st.nextElement();
                // Proccess the expires field
                if (s1.startsWith(" expires=")) {
                    s1 = s1.substring(s1.indexOf("=") + 1);
                    try {
                        // Convert the Date especification to Age format of Servlets Cookie
                        // Acording to non deprected api of JDK 1.1
                        DateFormat df = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss zzz", java.util.Locale.US);
                        Date expire = df.parse(s1);
                        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
                        cal.setTime(expire);
                        if (cal.get(1) == 1990 && cal.get(2) == 0 && cal.get(0) == 1) {
                            // Option remove cookie
                            age = 0;
                        } else {
                            Date now = new Date();
                            age = (expire.getTime() - now.getTime()) / 1000;
                            age = (age < 0 ? -1 : age);
                        }
                        choc_chip.setMaxAge((int)age);
                        //System.out.println("Age =>" + choc_chip.getMaxAge());
                    } catch (Exception e) {
                        if (getLogger().isDebugEnabled())
                            getLogger().warn("Invalid Date format en cookie String");
                    }
                } // end if expires=
                // Proccess the path field
                if (s1.startsWith(" path=")) {
                    // Set Path
                    choc_chip.setPath(s1.substring(s1.indexOf("=") + 1));
                    //System.out.println("Path =>" + choc_chip.getPath());
                } // end if path=
                // Proccess the domain field
                if (s1.startsWith(" domain=")) {
                    // Set Domain
                    choc_chip.setDomain(s1.substring(s1.indexOf("=") + 1));
                    //System.out.println("Domain =>" + choc_chip.getDomain());
                } // end if domain=
                // Proccess the secure flags
                if (s1.startsWith(" secure")) {
                    // Set Secure
                    choc_chip.setSecure(true);
                    //System.out.println("Secure");
                } // end if secure
            } // end while st.hasMoreElements
            // Return the cookie to the caller
            return choc_chip;
        }

        /** Returns the generated page (Reader) to the browser check for the header to set the response object */
        public void showPage(HttpServletResponse res, Parser parser, Content page) throws Exception {
            char[] buff_out = new char[8192];
            BufferedReader in = new BufferedReader(page.getPage(), 8192);
            StringBuffer xmlPage = new StringBuffer(8192);
            int i;
            String s;
            s = in.readLine();
            if (this.actionResultMap!=null) // If its called by an action set a default return value.
              this.objectModel.put(DBPrismAction.ACTION_SUCCESS_OBJECT,Boolean.FALSE);
            if (s.startsWith("Location: ") || 
                s.startsWith("Set-Cookie: ") ||
                s.startsWith("Content-type: ") ||
                s.startsWith("Content-length: ") ||
                s.startsWith("WWW-authenticate: ") ||
                s.startsWith("X-Cocoon-") ||
                s.startsWith("Status: "))
                do {
                    if (s.startsWith("Content-type: ") || s.startsWith("Content-length: "))
                        continue; // ignore due is set into Cocoon's serialization stage
                    else if (s.startsWith("Location: ")) {
                        s = s.substring(10);
                        res.sendRedirect(s);
                        // LXG: changed to static access
                        res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
                        if (getLogger().isDebugEnabled())
                          getLogger().debug("Location header: " + s);
                        return;
                    } else if (s.startsWith("WWW-authenticate: ")) {
                        String reason = s.substring(18).trim();
                        if (getLogger().isDebugEnabled())
                            getLogger().debug("NotAuthorizedException Header (" + reason + ")");
                        sendUnauthorized(res, reason);
                        parser.parse(new InputSource(new StringReader(DBPrism.UnauthorizedText)),this.xmlConsumer);
                        return;
                    } else if (s.startsWith("Set-Cookie: ")) {
                        Cookie choc_chip = Make_Cookie(s.substring(12));
                        res.addCookie(choc_chip);
                        if (getLogger().isDebugEnabled())
                            getLogger().debug("Set-Cookie header: " + s.substring(12));
                    } else if (this.actionResultMap!=null) {
                        // If DBPrismGenerator is executed as Action interprets these headers.
                        if (s.startsWith("X-Cocoon-Result: "))
                          try {
                          String sParam = s.substring(17);
                          i = sParam.indexOf('=');
                          this.actionResultMap.put(sParam.substring(0,i),sParam.substring(i+1));
                          if (getLogger().isDebugEnabled())
                              getLogger().debug("X-Cocoon-Result header: " + sParam);
                          } catch (IndexOutOfBoundsException e) {
                            if (getLogger().isWarnEnabled())
                             getLogger().warn("DBPrismGenerator found a malformed header: " + s);
                          }
                        else if (s.startsWith("X-Cocoon-Redirect: "))
                          try {
                            // If DBPrismGenerator is executed as Action interprets this header.
                            String sParam = s.substring(19);
                            i = sParam.indexOf(';');
                            boolean sessionMode = "true".equalsIgnoreCase(sParam.substring(i+1));
                            this.actionRedirector.redirect(true,sParam.substring(0,i));
                            if (getLogger().isDebugEnabled())
                                getLogger().debug("X-Cocoon-Redirect header: " + sParam);
                          } catch (IndexOutOfBoundsException e) {
                            if (getLogger().isWarnEnabled())
                                getLogger().warn("DBPrismGenerator found a malformed header: " + s);
                          }
                        else if (s.startsWith("X-Cocoon-Success: "))
                          try {
                            // If DBPrismGenerator is executed as Action interprets this header.
                            if ("true".equalsIgnoreCase(s.substring(18)))
                              this.objectModel.put(DBPrismAction.ACTION_SUCCESS_OBJECT,Boolean.TRUE);
                            else
                              this.objectModel.put(DBPrismAction.ACTION_SUCCESS_OBJECT,Boolean.TRUE);
                            if (getLogger().isDebugEnabled())
                              getLogger().debug("X-Cocoon-Success header: " + s.substring(18));
                          } catch (IndexOutOfBoundsException e) {
                            if (getLogger().isWarnEnabled())
                               getLogger().warn("DBPrismGenerator found a malformed header: " + s);
                          }
                    } else
                        try {
                          i = s.indexOf(':');
                          res.setHeader(s.substring(0, i), s.substring(i + 2));
                          if (getLogger().isDebugEnabled())
                              getLogger().debug("SetHeader : " + s);
                        } catch (IndexOutOfBoundsException e) {
                          if (getLogger().isWarnEnabled())
                             getLogger().warn("DBPrismGenerator found a malformed header: " + s);
                        }
                } while ((s = in.readLine()) != null && s.length() > 0);
                // End while header lines
            // Output the rest of generated page in htp.htbuf
            // send it without pay attention to new lines
            while ((i = in.read(buff_out)) > 0)
                xmlPage.append(buff_out, 0, i);
            parser.parse(new InputSource(new StringReader(xmlPage.toString())),this.xmlConsumer);
        }

        /**
         * Parallel content agregation Problem: DBPrismGenrator divides the XML generation in two stages
         * at setup time DBPrismGenerator create a private Thread (delayer) which
         * calls to DBPrism.makePage method, then permits to Cocoon2's threads to continue with other process.
         * At setup time DBPrismGenerator uses Block class as private semaphore which
         * locks generate method until the makePage operation is finished.
         * When makePage finish (run method of Delayer) unlock the semaphore permiting
         * to DBPrismGenerator to continue with the generation step calling to DBPrism.getPage() flow:
         * - Cocoon2 setup stage (in parallel) --> setup (part1) (Start a private engine 1 in parallel)
         * --> setup (part2) (Start a private engine 2 in parallel) --> setup (partn) (Start a private engine 1 in parallel)
         * - Cocoon2 generate stage (Sequencially gets the content of the parts)
         * --> generate (part1) (Wait until engine 1 finish) --> generate (part2) (Wait until engine 2 finish)
         * --> generate (partn) (Wait until engine n finish) - Serialize stage.
         */
        class Delayer extends Thread {
            static final int STOPED = 0;
            static final int RUNNING = 1;
            static final int NOTAUTOHRIZED = 2;
            static final int GENERALERROR = 3;
            static final int FINISHED = 4;
            int status = STOPED;
            String msg = "OK";
            private DBPrism dbEngine = null;
            private CocoonRequestWrapper theRequest = null;
            private Block theBlock = null;

            public Delayer(DBPrism eng, CocoonRequestWrapper request, Block blocker) throws ProcessingException {
                if (eng == null || request == null) // sanity checks
                        throw new ProcessingException("DBPrism engine or request is null in Delayer");
                dbEngine = eng;
                theRequest = request;
                theBlock = blocker;
                status = STOPED;
            }

            public void run() {
                //System.out.println("Starting request to engine = "+dbEngine.toString());
                status = RUNNING;
                try {
                    dbEngine.makePage(req);
                    status = FINISHED;
                } catch (NotAuthorizedException ne) {
                    status = NOTAUTOHRIZED;
                    msg = ne.getMessage();
                } catch (Exception e) {
                    status = GENERALERROR;
                    msg = e.getMessage();
                } finally {
                    theBlock.unlock(dbEngine); // Unblock the engine
                    //System.out.println("Ending request to engine = "+dbEngine.toString());
                    //System.out.println("Return Status ="+status);
                    //System.out.println("Return Message="+msg);
                    dbEngine = null;
                    theRequest = null;
                    theBlock = null;
                }
            }
        } // End inher class Delayer
}
